/** \file    file_dir.cpp
  * \brief   Filename manipulations and directory control routines.
  * \date    2002 - 2004
  * \author  Fedor Chelnokov
  * \version $Header: /home/cscom1r4/cvsroot/cs/mswin/src/file_dir.cpp,v 1.7 2007/07/12 20:30:10 cscom1r4 Exp $
  */

#include <io.h>
#include <direct.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <mswin/file_dir.h>
#include <sstream>
#include <algorithm>
#include <cassert>
#include <cctype>

namespace mswin {

bool remove(const char *f)
{
	return ::remove(f) == 0;
}

bool file_exists(const char *f)
{
	struct _stat s;
	return 0 == _stat(f, &s) && (s.st_mode & _S_IFREG);
}

bool exists(const char *f)
{
	return !_access(f, 0);
}

bool readable(const char *f)
{
	return !_access(f, 04);
}

bool writable(const char *f)
{
	return !_access(f, 02);
}

std::string basename(const std::string & fn)
{
	char fname[_MAX_FNAME];
	_splitpath(fn.c_str(), 0, 0, fname, 0);
	return fname;
}

std::string filename(const std::string & fn)
{
	char fname[_MAX_FNAME], ext[_MAX_EXT];
	_splitpath(fn.c_str(), 0, 0, fname, ext);
	if (ext[1] == 0)
		return fname;
	else
		return std::string(fname) + ext;
}

std::string getcwd()
{
   char buffer[_MAX_PATH];
   _getcwd(buffer, _MAX_PATH);
   return buffer;
}

cwd::cwd(const std::string & dir)
	: _temp(getcwd()) { _chdir(dir.c_str()); }

cwd::~cwd()
	{ _chdir(_temp.c_str()); }


void rename(const std::string & oldname, const std::string & newname)
{
	if (0 != ::rename(oldname.c_str(), newname.c_str())) {
		std::string report = "failed to rename \"";
		report += oldname;
		report += "\" into \"";
		report += newname;
		report += "\"";
		throw std::runtime_error(report);
	}
}

bool mkdir(const char * name)
{
	return _mkdir(name) == 0 || errno == EEXIST;
}

void change_ext(std::string & path, const std::string &newext)
{
	char drive[_MAX_DRIVE], dir[_MAX_DIR], title[_MAX_FNAME], ext[_MAX_EXT];
	_splitpath(path.c_str(), drive, dir, title, 0);

	if (newext.length() + 1 <= _MAX_EXT)
		strcpy(ext, newext.c_str());
	else
		throw std::runtime_error("too long extension");

	char newpath[_MAX_PATH];
	_makepath(newpath, drive, dir, title, ext);
	path = newpath;
}

bool equal_ext(const std::string & path, const std::string & ext)
{
	std::string path_ext;
	splitpath(path, 0, 0, 0, &path_ext);
	if (ext.empty())
		return path_ext.size() == 1;
	if (ext[0] == '.') 
		return _stricmp(ext.c_str(), path_ext.c_str()) == 0;
	return _stricmp(ext.c_str(), path_ext.substr(1).c_str()) == 0;
}

std::string subdir_file(const std::string &dir, const std::string &subdir, bool create_subdir,
					    const std::string &basename, const std::string &ext)
{
	char path[_MAX_PATH];
	if (dir.length() + 2 <= _MAX_PATH) {
		strcpy(path, dir.c_str());
		char c = (dir.length() != 0) ? path[dir.length()-1] : ' ';
		if (c != '\\' && c != '/')
			strcat(path, "\\");
	} else
		throw std::runtime_error("too deep directory");

	char split_drive[_MAX_DRIVE], split_dir[_MAX_DIR], split_fname[_MAX_FNAME], split_ext[_MAX_EXT];
	_splitpath(path, split_drive, split_dir, split_fname, split_ext);

	if (subdir.length() + strlen(split_dir) + 1 <= _MAX_DIR)
		strcat(split_dir, subdir.c_str());
	else
		throw std::runtime_error("too deep directory");

	if (create_subdir) {
		_makepath(path, split_drive, split_dir, 0, 0); //path = split_drive + split_dir
		if (!exists(path))
			_mkdir(path);
	}

	if (basename.length() + 1 <= _MAX_FNAME)
		strcpy(split_fname, basename.c_str());
	else
		throw std::runtime_error("too long file name");

	if (ext.length() + 1 <= _MAX_EXT)
		strcpy(split_ext, ext.c_str());
	else
		throw std::runtime_error("too long extension");

	_makepath(path, split_drive, split_dir, split_fname, split_ext);
	return path;
}

void splitpath(const std::string & path, std::string * pdrive, std::string * pdir, std::string * ptitle, std::string * pext)
{
	char drive[_MAX_DRIVE], dir[_MAX_DIR], title[_MAX_FNAME], ext[_MAX_EXT];
	_splitpath(path.c_str(), drive, dir, title, ext);
	if (pdrive != 0)
		pdrive->assign(drive, strlen(drive));
	if (pdir != 0)
		pdir->assign(dir, strlen(dir));
	if (ptitle != 0)
		ptitle->assign(title, strlen(title));
	if (pext != 0)
		pext->assign(ext, strlen(ext));
}

std::string makepath(const std::string * pdrive, const std::string * pdir, const std::string * ptitle, const std::string * pext)
{
	char path[_MAX_PATH];
	const char * drive = 0, * dir = 0, * title = 0, * ext = 0;
	if (pdrive != 0) {
		if (pdrive->size() > _MAX_DRIVE)
			throw std::runtime_error("drive too long");
		drive = pdrive->c_str();
	}
	if (pdir != 0) {
		if (pdir->size() > _MAX_DIR)
			throw std::runtime_error("path too long");
		dir = pdir->c_str();
	}
	if (ptitle != 0) {
		if (ptitle->size() > _MAX_FNAME)
			throw std::runtime_error("file name too long");
		title = ptitle->c_str();
	}
	if (pext != 0) {
		if (pext->size() > _MAX_EXT)
			throw std::runtime_error("extension too long");
		ext = pext->c_str();
	}
	_makepath(path, drive, dir, title, ext);
	return std::string(path);
}

char * fullpath(char * abs_path, const char * rel_path, size_t max_length)
{
	return _fullpath(abs_path, rel_path, max_length);
}

std::string fullpath(const char * rel_path)
{
	char abs_path[_MAX_PATH];
	_fullpath(abs_path, rel_path, _MAX_PATH);
	return abs_path;
}

std::string parent(const std::string & path)
{
	if (path.empty())
		return path;
	std::string drive, dir;
	if (path[path.size() - 1] == '\\' || path[path.size() - 1] == '/')
		splitpath(path.substr(0, path.size()-1), &drive, &dir, 0, 0);
	else
		splitpath(path, &drive, &dir, 0, 0);
	return makepath(&drive, &dir, 0, 0);
}

std::string child(const std::string & path)
{
	std::string title, ext;
	splitpath(path, 0, 0, &title, &ext);
	return title + ext;
}

bool createpath(std::string path)
{
	if (path[path.size() - 1] == '\\' || path[path.size() - 1] == '/')
		path.erase(path.size() - 1);
	if (path.empty() || exists(path))
		return true;
	if (!createpath(parent(path)))
		return false;
	return _mkdir(path.c_str()) == 0;
}

void standardize_path(std::string & path)
{
	std::string::iterator i;
	for (i = path.begin(); i != path.end(); ++i)
		if (*i == '/')
			*i = '\\';
		else
			*i = std::tolower(*i);
}

bool is_absolute(std::string const & path)
{
	if (path.empty())
		return false;
	return path[0] == '/' || path[0] == '\\' || path.size() >= 2 && path[1] == ':';
}

std::string relative(std::string path, std::string base)
{
	if (path.empty())
		return path;
	standardize_path(path);
	standardize_path(base);
	if (base[base.size() - 1] != '\\')
		base.append(1, '\\');
	std::string::size_type first = 0, last = 0, pos;
	while (first < base.size()) {
		last = base.find('\\', first);
		//remember about directories with common prefix
		if (base.compare(first, last - first + 1, path, first, last - first + 1) != 0)
			break;
		first = last + 1;
	}
	//'first' marks position where two paths become distinct
	if (first == 0 && is_absolute(base)) {
		///paths are on different drives, return absolute path
		return path;
	}
	pos = first;
	std::ostringstream out;
	while (first < base.size()) {
		last = base.find('\\', first);
		out << "..\\";
		first = last + 1;
	}
	out << path.substr(pos);
	return out.str();
}

std::string absolute(std::string path, std::string base)
{
	if (path.empty())
		return base;
	if (is_absolute(path))
		return path;
	standardize_path(path);
	standardize_path(base);
	if (base[base.size() - 1] != '\\')
		base.append(1, '\\');
	std::string::size_type bpos = base.size() - 1, ppos = 0;
	while (path.compare(ppos, 3, "..\\") == 0) {
		ppos += 3;
		bpos = base.rfind('\\', bpos - 1);
	}
	if (path.compare(ppos, 2, ".\\") == 0) {
		ppos += 2;
	}
	std::ostringstream out;
	out << base.substr(0, bpos + 1) << path.substr(ppos);
	return out.str();
}

bool same_drive(const std::string & path1, const std::string & path2)
{
	std::string drive1, drive2;
	splitpath(path1, &drive1, 0, 0, 0);
	splitpath(path2, &drive2, 0, 0, 0);
	return drive1 == drive2;
}

void enum_dir::open(const std::string & filespec) {
	assert(!_open);
	_finddata_t fileinfo;
	_handle = _findfirst(filespec.c_str(), &fileinfo);
	if (-1 != _handle) {
		_open = true;
		_name = fileinfo.name;
		_dir = (fileinfo.attrib & _A_SUBDIR) != 0;
	}
}

void enum_dir::close() {
	if (_open) {
		_findclose(_handle);
		_open = false;
	}
}

void enum_dir::findnext() {
	_finddata_t fileinfo;
	if (_findnext(_handle, &fileinfo))
		return close();
	_name = fileinfo.name;
	_dir = (fileinfo.attrib & _A_SUBDIR) != 0;
}

void enum_file::open(const std::string & filespec) {
	enum_dir::open(filespec);
	if (!end() && is_dir())
		findnext();
}

void enum_file::findnext() {
	do {
		enum_dir::findnext();
	} while (!end() && is_dir());
}

} //namespace mswin
